/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2025 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit

import net.ccbluex.liquidbounce.config.types.NamedChoice
import net.ccbluex.liquidbounce.config.types.nesting.ToggleableConfigurable
import net.ccbluex.liquidbounce.event.tickHandler
import net.ccbluex.liquidbounce.features.module.Category
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.incrementSpeed
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.speed
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.triggerHealth
import net.ccbluex.liquidbounce.utils.client.*
import net.ccbluex.liquidbounce.utils.entity.moving
import net.ccbluex.liquidbounce.utils.kotlin.Priority
import net.minecraft.entity.effect.StatusEffectCategory
import net.minecraft.entity.effect.StatusEffects
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket
import kotlin.math.max
import kotlin.math.min

/**
 * Time Shift module
 * Formerly known as Regen, Zoot or any other module that abuses Minecraft 1.8 client-sided mechanics
 *
 * Regen and Zoot are combined into this module to keep the code clean and organized
 * and to prevent multiple modules executing time shift at the same time.
 *
 * FastUse on the other hand is a separate module because it has a few options that are not
 * related to time shift.
 */
object ModuleTimeShift : ClientModule("TimeShift", Category.EXPLOIT, disableOnQuit = true,
    aliases = listOf("Regen", "Zoot")
) {

    private object Health : ToggleableConfigurable(this, "Health", true) {

        /**
         * The health to trigger the speed
         */
        private val triggerHealth by int("Health", 16, 0..20)

        /**
         * Calculate the speed to reach the desired health
         *
         * ([triggerHealth] - [player.health]) * [incrementSpeed] = [speed]
         */
        private val incrementSpeed by int("Increment", 20, 0..60)

        val speed: Int
            get() = max(0, ((triggerHealth - player.health) * incrementSpeed).toInt())

    }

    init {
        tree(Health)
    }

    private val badEffects by boolean("BadEffects", false)
    private val fire by boolean("Fire", false)

    private val maximumSpeed by int("MaximumSpeed", 100, 5..200)
    private val timer by float("Timer", 1.0f, 0.1f..10f)

    private val conditions by multiEnumChoice("Conditions", Conditions.NOT_IN_THE_AIR)

    /**
     * The packet type to send to speed up item usage.
     *
     * @see PacketType for more information.
     * @see PlayerMoveC2SPacket for more information about the packet.
     *
     * PacketType FULL is the most likely to bypass, since it uses the C06 duplicate exempt exploit.
     *
     * AntiCheat: Grim
     * Tested AC Version: 2.5.34
     * Tested on: eu.loyisa.cn, anticheat-test.com
     * Usable MC version: 1.17-1.20.6
     * Q: Why this works?
     * A: https://github.com/GrimAnticheat/Grim/blob/9660021d024a54634605fbcdf7ce1d631b442da1/src/main/java/ac/grim/grimac/checks/impl/movement/TimerCheck.java#L99
     */
    private val packetType by enumChoice("PacketType", MovePacketType.FULL)

    override fun onEnabled() {
        if (!isOlderThanOrEqual1_8) {
            chat(markAsError(message("warningNot1_8")))
        }
        super.onEnabled()
    }

    val repeatable = tickHandler {
        // Check if the player is in creative mode or dead, if so, skip the time shift
        if (player.abilities.creativeMode || player.isDead) {
            return@tickHandler
        }

        if (conditions.any { it.test() }) {
            return@tickHandler
        }

        // Calculate the tick speed
        var tickSpeed = 0

        if (Health.enabled) {
            tickSpeed = max(tickSpeed, Health.speed)
        }

        if (fire && player.isOnFire && !player.isTouchingWaterOrRain) {
            tickSpeed = max(tickSpeed, 9)
        }

        if (badEffects) {
            player.activeStatusEffects
                .filter { (effect, status) -> effect.value().category == StatusEffectCategory.HARMFUL
                    && !status.isInfinite }
                .maxByOrNull { it.value.duration }?.let { (_, status) ->
                    tickSpeed = max(tickSpeed, min(status.duration / 20, maximumSpeed))
                }
        }

        if (tickSpeed > 0) {
            Timer.requestTimerSpeed(timer, Priority.IMPORTANT_FOR_USAGE_1, this@ModuleTimeShift)

            repeat(tickSpeed) {
                network.sendPacket(packetType.generatePacket())
            }
        }
    }

    @Suppress("unused")
    private enum class Conditions(
        override val choiceName: String,
        val test: () -> Boolean
    ) : NamedChoice {
        NOT_IN_THE_AIR("NotInTheAir", {
            !player.isOnGround
        }),
        NOT_DURING_MOVE("NotDuringMove", {
            player.moving
        }),
        NOT_DURING_REGENERATION("NotDuringRegeneration", {
            player.hasStatusEffect(StatusEffects.REGENERATION)
        }),
        DO_NOT_CAUSE_HUNGER("DoNotCauseHunger", {
            player.hungerManager.foodLevel < 20
        })
    }
}
